#include <RenderSubSystem/cvIMRenderer/LinePrimitiveMemoryManager.h>
#include <RenderSubSystem/PrimitiveArray.h>
#include <cstring>
#include <DebugAlloc.h>
#include <General/cvString.h>

namespace cvImmediateModeRenderer
{
	// PrimitiveArray pool
	cvPool LinePrimitiveMemoryManager::m_primitivepage_pool
	(sizeof(LinePrimitiveMemoryManager::primitivepage_t));

	// Renderbuffer pool
	cvPool LinePrimitiveMemoryManager::m_renderbuffer_pool(sizeof(line_struct));



	LinePrimitiveMemoryManager::LinePrimitiveMemoryManager()
		: m_renderbuffer_size(0), m_renderbuffer(0), m_read_begin(0), m_read_repl(0)
	{
		debug_alloc_page::instance();
		debug_alloc_renderbuffer::instance();
		// Allocate new page
		__init_primitivepage();
		LOGDEBUG("::RENDER(): Immediate mode line renderer redy.");
	}

	LinePrimitiveMemoryManager::~LinePrimitiveMemoryManager()
	{
		if(m_renderbuffer)
		{
			//m_renderbuffer_pool.ordered_free(m_renderbuffer, m_renderbuffer_size);
			debug_alloc_renderbuffer::instance().free(m_renderbuffer);
			m_renderbuffer = 0;
			m_renderbuffer_size = 0;
		}
		m_usedslots.clear();
		m_freeslots.clear();
		m_pagelist.clear_and_dispose(pagelist_disposer());
	}

	void LinePrimitiveMemoryManager::__init_primitivepage()
	{
		// Allocate new page
		primitivepage_t * page = ::new(debug_alloc_page::instance().malloc(1)) primitivepage_t();
		//primitivepage_t * page = ::new(m_primitivepage_pool.malloc()) primitivepage_t();
		std::memset(page->m_used, false, PRIMITIVESPERPAGE * sizeof(bool));

		for(size_t i = 0; i < PRIMITIVESPERPAGE; ++i)
		{
			m_freeslots.insert(std::make_pair(page->m_primitives + i, page));
		}
		// add it to page list
		m_pagelist.push_back(*page);
	}

	void * LinePrimitiveMemoryManager::accuirePrimitive()
	{
		primitivemap_it_t it = m_freeslots.begin();
		if(it != m_freeslots.end())
		{
			primitivepage_t * page = it->second;

			for(size_t i = 0; i < PRIMITIVESPERPAGE; ++i)
			{
				if(!page->m_used[i])
				{
					m_freeslots.erase(it);		// erase from free slots
					m_usedslots.insert(std::make_pair(page->m_primitives+i, page));	// insert to used slots
					page->m_used[i] = true;
					page->m_count++;
					return page->m_primitives+i;
				}
			}
			LOGERROR("::RENDER()#2: LinePrimitiveMemoryManager datastructes have been courrupted?");
			return NULL;
		}
		else
		{
			// Allocate new page
			primitivepage_t * page = ::new(debug_alloc_page::instance().malloc(1)) primitivepage_t();
			//primitivepage_t * page = ::new(m_primitivepage_pool.malloc()) primitivepage_t();
			// insert first slot to used.
			m_usedslots.insert(std::make_pair(page->m_primitives, page));
			page->m_used[0] = true;
			// register rest as free.
			for(size_t i = 1; i < PRIMITIVESPERPAGE; ++i)
			{
				debug_alloc_page::instance().check(page->m_primitives + i);
				m_freeslots.insert(std::make_pair(page->m_primitives + i, page));
				page->m_used[i] = false;
			}
			// append to pagelist
			m_pagelist.push_back(*page);

			LOGDEBUG("::RENDER()#2: allocated new primitivearray..");
			LOGDEBUG("address [" + stringify(page->m_primitives) + ", " + stringify(page->m_primitives + PRIMITIVESPERPAGE) + "]");
			return page->m_primitives;
		}
	}

	void LinePrimitiveMemoryManager::releasePrimitive(void * prim)
	{
		debug_alloc_page::instance().check(prim);
		primitivemap_it_t it = m_usedslots.find((line_struct*)prim);
		if(it != m_usedslots.end())
		{
			primitivepage_t * page = it->second;
			size_t primindex = ((size_t)prim - (size_t)page->m_primitives) / sizeof(line_struct);
			page->m_count--;
			page->m_used[primindex] = false;

			if(m_pagelist.size() > 1 && page->m_count == 0)
			{
				// Destroy page
				m_usedslots.erase(it);
				pagelist_it_t pit = pagelist_t::s_iterator_to(*page);
				for(size_t i = 0; i < PRIMITIVESPERPAGE; ++i)
				{
					m_freeslots.erase(page->m_primitives+i);
				}
				m_pagelist.erase_and_dispose(pit, pagelist_disposer());
				LOGDEBUG("::RENDER()#2: destructed empty primitivearray");
			}
			else
			{
				m_freeslots.insert(*it);	// insert to free slots
				m_usedslots.erase(it);		// erase from used slots
			}
			debug_alloc_page::instance().check();
		}
		else
		{	// FIXME: we get here. something is WRONG.
			LOGERROR("::RENDER()#2: Tried to release unexisting primitive: " + stringify(prim));
			it = m_freeslots.find((line_struct*)prim);
			if(it != m_freeslots.end())
			{
				LOGCRITICAL("::RENDER()#2: address was in free map!?!");
				throw debug::bad_pointer();
			}
			else
			{

			}
		}
	}

	void LinePrimitiveMemoryManager::pagelist_disposer::operator()
	(cvRenderInterface::PrimitiveArrayBase * page)
	{
		//m_primitivepage_pool.free(page);
		debug_alloc_page::instance().free(page);
	}

	void LinePrimitiveMemoryManager::purge()
	{
		m_usedslots.clear();
		m_freeslots.clear();
		m_pagelist.clear_and_dispose(pagelist_disposer());
		__init_primitivepage();
	}

	void LinePrimitiveMemoryManager::beginTransport(size_t prims)
	{
		// Allocate storgate for @prims count of line primitives
		if(prims > 0)
		{
			if(!m_renderbuffer)
			{
				m_renderbuffer = static_cast<line_struct*>(debug_alloc_renderbuffer::instance().malloc(prims));
				//m_renderbuffer = static_cast<line_struct*>(m_renderbuffer_pool.ordered_malloc(prims));
				m_renderbuffer_size = prims;
			}
			else
			{
				if(prims > m_renderbuffer_size)
				{	// buffer is too small
					__reserve_renderbuffer(prims);
				}
				else if(prims < m_renderbuffer_size / 2)
				{	// wasting too much space..
					__reserve_renderbuffer(prims);
				}
			}
			m_renderbuffer_write = m_renderbuffer;
		}

		m_read_begin = NULL;
		m_read_repl = 0;
		m_renderinputs.verticle_cnt = 0;
	}

	void LinePrimitiveMemoryManager::__reserve_renderbuffer(size_t prims)
	{
		//m_renderbuffer_pool.ordered_free(m_renderbuffer, m_renderbuffer_size);
		//m_renderbuffer = static_cast<line_struct*>(m_renderbuffer_pool.ordered_malloc(prims));

		debug_alloc_renderbuffer::instance().free(m_renderbuffer);
		m_renderbuffer = static_cast<line_struct*>(debug_alloc_renderbuffer::instance().malloc(prims));
		m_renderbuffer_size = prims;

	}

	void LinePrimitiveMemoryManager::transportNode(void * prim)
	{
		// Try copy the data in biggest possible chunks as we can.
		debug_alloc_page::instance().check(prim);
		if(m_read_begin == NULL)
		{
			m_read_begin = static_cast<line_struct*>(prim);
			m_read_repl = 1;
		}
		else if((m_read_begin+m_read_repl) != prim)
		{
			debug_alloc_page::instance().check(m_read_begin);
			debug_alloc_page::instance().check(m_read_begin + m_read_repl);
			// copy the chunk
			std::memcpy(m_renderbuffer_write, m_read_begin, sizeof(line_struct) * m_read_repl);

			debug_alloc_renderbuffer::instance().check();

			m_renderbuffer_write += m_read_repl;
			m_renderinputs.verticle_cnt += m_read_repl * 2;
			// start over
			m_read_begin = static_cast<line_struct*>(prim);
			m_read_repl = 1;
		}
		else if((m_read_begin+m_read_repl) == prim)
		{	// happily incease the chunk size to be copied. :)
			++m_read_repl;
		}
	}


	void * LinePrimitiveMemoryManager::getTransportRange(size_t & pagesz)
	{	// TODO: write-once primitive transport.
		pagesz = PRIMITIVESPERPAGE;
		return NULL;
	}

	void LinePrimitiveMemoryManager::finalizeTransport()
	{	// TODO: write-once primitive transport.
		if(m_read_begin)
		{
			debug_alloc_page::instance().check(m_read_begin +  m_read_repl);
			debug_alloc_renderbuffer::instance().check(m_renderbuffer_write);
			// copy the last remaining chunk.
			std::memcpy(m_renderbuffer_write, m_read_begin, sizeof(line_struct) * m_read_repl);
		}

		m_renderinputs.pverticles = m_renderbuffer;
		m_read_begin = 0;
		m_read_repl = 0;

	}

	void * LinePrimitiveMemoryManager::getRenderInputs()
	{
		return &m_renderinputs;
	}

};
